class Token{
  constructor(type,literal,lineNumber){
    this.tokenType = type
    this.literal = literal
    this.lineNumber = lineNumber
  }

  type(){
    return this.tokenType
  }

  literal(){
    return this.literal
  }

  lineNumber(){
    return this.lineNumber
  }
}

class Lexer{
  constructor(sourceCode){
    this.initTokenType()
    this.initKeywords()
    this.sourceCode = sourceCode
    this.position = 0
    this.readPosition = 0
    this.lineCount = 0
    this.ch = ''

    this.observer = null
    this.observerContext = null
  
  }

  initTokenType(){
    this.ILLEGAL = -2
    this.EOF = -1
    this.LET = 0
    this.IDENTIFIER = 1
    this.EQUAL_SIGN = 2
    this.PLUS_SIGN = 3
    this.INTEGER = 4
    this.SEMICOLON = 5

    this.IF = 6
    this.ELSE = 7

    this.PARENTHESES_LEFT = 8
    this.PARENTHESES_RIGHT = 9
    this.BRACE_LEFT = 10
    this.BRACE_RIGHT = 11
    this.LT = 12
    this.RT = 13
    this.MINUS = 14
    this.PLUS = 15
  }


  initKeywords(){
    this.keyWordMap = []
    this.keyWordMap['let'] = new Token(this.LET,"let",0)
    this.keyWordMap['if'] = new Token(this.IF,'if',0)
    this.keyWordMap['else'] = new Token(this.ELSE,'else',0)
  }

  setLexingObserver(o,context){
    if(o !== null && o !== undefined){
      this.observer = o
      this.observerContext = context
    }
  }

  getKeyWords(){
    return this.keyWordMap
  }

  readChar(){
    if (this.readPosition >= this.sourceCode.length){
      this.ch = 0
    }else {
      this.ch = this.sourceCode[this.readPosition]
    }
    this.readPosition++
  }

  skipWhiteSpaceAndNewLine() {
    while( this.ch === ' ' || this.ch === '\t'  || this.ch === '\n' || this.ch === '\u00a0'){
      if(this.ch === '\t' || this.ch === '\n'){
        this.lineCount++
      }
      this.readChar()
    }
  }

  nextToken() {
    var tok
    this.skipWhiteSpaceAndNewLine()
    var lineCount = this.lineCount  
    this.position = this.readPosition - 1  //token的起始字符位置从有第一个字符算起

    //未知 的字符会使程序报错
    switch(this.ch){
      case '=':
        tok = new Token(this.EQUAL_SIGN,"=",lineCount)
        break
      case ';':
        tok = new Token(this.SEMICOLON,";",lineCount)
        break
      case '+':
        tok = new Token(this.PLUS_SIGN,'+',lineCount)
        break
      case '(':
        tok = new Token(this.PARENTHESES_LEFT,'(',lineCount)
        break;
      case ')':
        tok = new Token(this.PARENTHESES_RIGHT,')',lineCount)
        break;
      case '{':
        tok = new Token(this.BRACE_LEFT,'(',lineCount)
        break;
      case '}':
        tok = new Token(this.BRACE_RIGHT,')',lineCount)
        break;
      case '>':
        tok = new Token(this.GT,'>',lineCount)
        break;
      case '<':
        tok = new Token(this.LT,'<',lineCount)
        break;
      case '+':
        tok = new Token(this.PLUS,'+',lineCount)
        break;
      case '-':
        tok = new Token(this.MINUS,'-',lineCount)
        break;
      case 0:
        tok = new Token(this.EOF,'',lineCount)
        break
      
      default:
        var res = this.readIdentifier()
        if (res !== false){
          if(this.keyWordMap[res] !== undefined){
            tok = this.keyWordMap[res]
          }else{
            tok = new Token(this.IDENTIFIER,res,lineCount)
          }        
        }else{
          res = this.readNumber()
          if(res !== false ){
            tok = new Token(this.INTEGER,res,lineCount)
          }
        }
      
      if (res === false) {
        throw "词法错误"
        tok = undefined
      }
    }
    
    let endPosition = this.readPosition -1
    this.readChar()

    if(tok !== undefined){
      this.notifyObserver(tok,endPosition)
    }
    return tok
  }

  notifyObserver(token,endPosition){
    //console.log(token,'observer')
    this.observer.notityTokenCreation(token,this.observerContext,this.position,endPosition)
  }

  isLetter(ch){
    return ('a' <= ch && ch <= 'z') || 
    ('A' <= ch && ch <= 'Z') ||
    (ch === '_')
  }
  readIdentifier() {
    var identifier = ""
    while(this.isLetter(this.ch)){
      identifier += this.ch
      this.readChar()
    }

    if(identifier.length > 0){
      return identifier
    }else {
      return false
    }
  }

  isDigit(ch){
    if(ch === 0) return false
    return '0' <= ch && ch <= '9'
  }
  readNumber(){
    var number = ""
    while(this.isDigit(this.ch)){
      number += this.ch
      this.readChar()
    }

    if(number.length >0){
      return number
    }else{
      return false
    }
  }

  lexing() {
    try{
      this.readChar()

      var tokens = []
      var token = this.nextToken()
  
      while(token.type() !== this.EOF) {
        tokens.push(token)
        token = this.nextToken()
      }
    }catch(e){
      alert("错误")
    }

  }
}

export default Lexer